/*
 *      ap_search.c from Access Point SNMP Utils for Linux
 *
 * Copyright (c) 2002 Roman Festchook <roma at polesye dot net>
 * Copyright (c) 2003 Jan Rafaj <jr-aputils at cedric dot unob dot cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 from
 * June 1991 as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#ifdef OS_SOLARIS
#include <sys/sockio.h>
#endif

#ifdef OS_X
#include <sys/socket.h>
#endif

#include <sys/socket.h>
#include <net/if.h>
#include <sys/time.h>
#include <errno.h>
#include "ap-utils.h"
#include "config.h"

#if defined (__GLIBC__)
#include <libgen.h>
#endif

#define SEARCH_COMMUNITY _("Community name: ")
#define SEARCH_HEADER _("  NUM  IP ADDRESS       MIB TYPE    FW TYPE (VERSION) AP NAME")
#define MAX_APS LAST_ROW-7

extern int atmel410_filter;
extern char *community;

char q_pressed = 0;
int i, f_ifctr;
struct faps {
    struct in_addr ip;
    int type;
} *fapsa;

void scan_local_segment (struct sockaddr_in *from, struct sockaddr_in *to,
    char *ifname)
{
    extern WINDOW *main_sub;
    extern char *ap_types[];
    unsigned char message[1024], *start;
    /* unsigned char name_fw[256], *name_p, *fwver_p; */
    extern int atmel410_filter; /* if called from ap-gl utility */

    
    /*
     * operAccessPointName OIDs used to detect AP MIB type [in order
     * of appearance in the 'for' loop below: ATMEL410, NWN, ATMEL12350]
     */
/*
    char operAccessPointName[3][12] = {
      {0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A, 0x01, 0x02, 0x01, 0x0A, 0x00},
      {0x2B, 0x06, 0x01, 0x02, 0x01, 0x01, 0x05, 0x00},
      {0x2B, 0x06, 0x01, 0x04, 0x01, 0xE0, 0x3E, 0x01, 0x02, 0x01, 0x0A, 0x00}
    };
*/
    /*
     * note: we'll rather use sysDescr OID for probing, becouse the devices
     * wont rely on correct community for this query
     */
    char sysDescr[3][12] = {
      {0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A, 0x01, 0x01, 0x01, 0x01, 0x00},
      {0x2B, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00},
      {0x2B, 0x06, 0x01, 0x04, 0x01, 0xE0, 0x3E, 0x01, 0x01, 0x01, 0x01, 0x00}
    };

    int scd_ap_type, last_searched_type = ATMEL12350;
    int c, s2, errno, len, client_len = SIZE;
    struct in_addr to_addr_reserv;
    struct sopts {
	int broad;
    } opts = { 1 };
    struct ip_mreq mult = { {0}, {INADDR_ANY} };
    varbind varbinds[1];
    fd_set rds;
    struct timeval timeout, starttime, curtime;
    struct timezone tz;

    if (!i)
	print_help(_("Please wait while scanning, or press 'Q' to quit."));

    if ((s2 = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
	print_helperr(ERR_SOCKET);
	getch();
	return;
    }

    if (bind(s2, (struct sockaddr *) from, SIZE) == -1) {
	print_helperr(ERR_SOCKET);
	getch();
	goto close_ret;
    }

    if (setsockopt (s2, SOL_SOCKET, SO_BROADCAST, &opts,
	sizeof(struct sopts)) == -1) {
	print_helperr(_("Can't set broadcast option on socket. "
	    "Press any key."));
	getch();
	goto close_ret;
    }

    inet_aton("224.0.1.43", &mult.imr_multiaddr);
    if (setsockopt(s2, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mult,
	sizeof(struct ip_mreq)) == -1) {
	print_helperr(_("Can't set multicast membership on socket. "
	    "Press any key."));
	getch();
	goto close_ret;
    }

    mvwaddstr(main_sub, LAST_ROW - 5, 1,
	_("Scanning via network interface:"));
    sprintf(message, _("  Index: %i"), f_ifctr);
    mvwaddstr(main_sub, LAST_ROW - 4, 1, message);
    sprintf(message, _("  Name: %s"), ifname);
    mvwaddstr(main_sub, LAST_ROW - 3, 1, message);
    sprintf(message, _("  IP: %s"), inet_ntoa(from->sin_addr));
    mvwaddstr(main_sub, LAST_ROW - 2, 1, message);

    if(atmel410_filter) {
	last_searched_type = ATMEL410;
    }	
    
    for (scd_ap_type = ATMEL410; scd_ap_type <= last_searched_type; scd_ap_type++) {
	clear_main_new(LAST_ROW - 1, LAST_ROW);
	sprintf(message, _("Scanning for AP with MIB type: %s"),
	    ap_types[scd_ap_type]);
	mvwaddstr(main_sub, LAST_ROW - 1, 1, message);
	wrefresh(main_sub);

/*	varbinds[0].oid = operAccessPointName[scd_ap_type];*/
	varbinds[0].oid = sysDescr[scd_ap_type];
	varbinds[0].len_oid = (scd_ap_type == NWN ?
/*	    8 : sizeof(operAccessPointName[scd_ap_type]));*/
	    8 : sizeof(sysDescr[scd_ap_type]));
	varbinds[0].len_val = 0;
	varbinds[0].type = NULL_VALUE;
	len = ber(message, varbinds, 1, GET);

	if (scd_ap_type == NWN) {
	    to_addr_reserv = to->sin_addr;
	    to->sin_addr = mult.imr_multiaddr;
	}

        errno = 0;
	if (sendto(s2, message, len, 0, (struct sockaddr *) to, SIZE) == -1) {
	    sprintf(message, _("Failure in sendto(): %s. Press any key."),
		strerror(errno));
	    print_helperr(message);
	    getch();
	    continue;
	}

	timeout.tv_sec = 2;
	timeout.tv_usec = 0;
	gettimeofday(&starttime, &tz);

	while (1) {

	    FD_ZERO(&rds);
	    FD_SET(s2, &rds);
	    FD_SET(0, &rds);

	    /*
	     * Compute time difference. Note that for portability reasons,
	     * we may not rely on select() below setting up timeout to
	     * remaining time upon its return, although this works on Linux.
	     */
	    gettimeofday(&curtime, &tz);
	    c = (curtime.tv_sec * 1000000 + curtime.tv_usec) -
		(starttime.tv_sec * 1000000 + starttime.tv_usec);
	    if (c < (2 * 1000000 + 0)) {
		c = (2 * 1000000 + 0) - c;
		timeout.tv_sec = c / 1000000;
		timeout.tv_usec = c - timeout.tv_sec * 1000000;
	    } else
		/* Return if nothing has been received after timeout secs. */
		break;

	    c = select(s2 + 1, &rds, NULL, NULL, &timeout);

	    /* error occured */
	    if (c == -1)
		break;

	    /* timed out */
	    if (c == 0)
		continue;

	    /* Key pressed. If it is 'Q', return. */
	    if (FD_ISSET(0, &rds)) {
		c = getc(stdin);
		if (c == 'q' || c == 'Q') {
		    q_pressed = 1;
		    goto close_ret;
		}
	    }

	    /* If data are available for reading on s2, try to read them now. */
	    if (FD_ISSET(s2, &rds))
		if ((len = recvfrom(s2, message, 512, 0,
		    (struct sockaddr *) from, &client_len)) == -1)
			continue;

	    start = message;
	    if (*start != ASN_HEADER)
		continue;

	    start += (start[1] & 0x80) ? (start[1] & 0x7F) + 2 : 2;
	    start += *(start + 4) + 5;

	    if (*start != RESPONSE)
		continue;

	    start += (start[1] & 0x80) ? (start[1] & 0x7F) + 2 : 2;

	    if (*(start + 5) || *(start + 9) != ASN_HEADER)
		continue;

	    start += (start[10] & 0x80) ? (start[10] & 0x7F) + 11 : 11;

	    if (*(start) != ASN_HEADER)
		continue;

	    start += (start[1] & 0x80) ? (start[1] & 0x7F) + 2 : 2;
	    start += *(start + 1) + 2;

	    if (start[1] & 0x80) {
		varbinds[0].len_val = start[2];
		start += (start[1] & 0x7F) + 2;
	    } else {
		varbinds[0].len_val = start[1];
		start += 2;
	    }

	    /*
	     * Dupe check. There are 2 reasons why to do this:
	     * 1. If interface-based IP aliases are used (f.e. on Linux),
	     *    it may well happen that their broadcast addresses may
	     *    inadvertedly overlap each other, so the same AP IPs would
	     *    answer twice (for a specific interface and its alias
	     *    device pass).
	     * 2. ATMEL410 devices are capable to answer both queries with
	     *    410 as well as 12350 IDs, while ATMEL12350 device only
	     *    answers queries with 12350 ID. Hence, we need to check
	     *    for duplicate responses from ATMEL410 devices, when
	     *    ATMEL410 check has already performed and ATMEL12350 one
	     *    is pending.
	     * Note, that ATMEL410 devices may, under certain circumstances,
	     * fail to pass the ATMEL410 check, and can be accidentally
	     * marked as an ATMEL12350 device. This is a known bug and
	     * should be eventually solved in any of upcomming releases (TODO).
	     */
	    if (i) {
		int dupcnt = 0, j = i - 1;

		do {
		    if (from->sin_addr.s_addr == fapsa[j].ip.s_addr)
			dupcnt++;
		} while (--j >= 0); 

		if (dupcnt)
		    continue;
	    }

	    /* new AP (unique IP/APtype pair) found */

	    fapsa = realloc(fapsa, (i + 1) * sizeof(struct faps));

	    fapsa[i].ip = from->sin_addr;
	    fapsa[i].type = scd_ap_type;

	    for (len = 0; len < varbinds[0].len_val && start[len]; len++);
	    start[len + 1] = '\0';

	    /*
	     * Returned sysDescr string is (hopefully!) always of format:
	     *             string1 (Ver. string2) string3
	     * where:
	     *   string1 = "802.11 X "
	     *     where: "X" identifies firmware type (AP, APP, WB, WA, etc)
	     *            unfortunately this is not deterministic across
	     *            manufacturers (each one uses different naming scheme)
	     *   string2 = firmware version
	     *   string3 = device name, as defined with operAccessPointName
	     *             OID
	    name_p = strchr(start, ')');
	    *name_p = '\0';
	    name_p += 2;
	    fwver_p = strstr(start, "(Ver. ");
	    fwver_p += strlen("(Ver. ");
	    sprintf(name_fw, "%s (%s)", name_p, fwver_p);

	    if (strlen(name_fw) > (size_t)(COLS - MCOLS - 37)) {
		name_fw[COLS - MCOLS - 38] = '>';
		name_fw[COLS - MCOLS - 37] = '\0';
	    }
	     */

	    if (strlen(start) > (size_t)(COLS - MCOLS - 37)) {
		/* truncate (limit to scr. size) */
		start[COLS - MCOLS - 38] = '>';
		start[COLS - MCOLS - 37] = '\0';
	    }

	    sprintf(message, "  %3i  %-15s  %-10s  %s", i,
		inet_ntoa(fapsa[i].ip), ap_types[fapsa[i].type], start);
	    i++;

	    mvwaddstr(main_sub, i, 0, message);
	    wrefresh(main_sub);

	    /* Bail out if the number of found devices exceeds sane limit. */
	    if (i == MAX_APS)
		goto close_ret;
	}

	if (scd_ap_type == NWN)
	    to->sin_addr = to_addr_reserv;

    }

close_ret:
    close(s2);
    return;
}

void ap_search()
{
    extern WINDOW *main_sub;
    int s1, ac;
    unsigned int ilen = 256;
    struct sockaddr_in from, to;
    struct ifconf ifc;
    struct ifreq *ifr;
    char *ifbuf_ptr = NULL, *ifrec_ptr;
/*
    char *old_community;
    char buf[16];

    print_help(_("Please enter SNMP community name that will be used for AP "
		 "detection."));
    mvwaddstr(main_sub, 0, 2, SEARCH_COMMUNITY);
    wrefresh(main_sub);
    get_pass(buf, 0, 2 + strlen(SEARCH_COMMUNITY), sizeof(buf));
    old_community = community;
    community = malloc(strlen(buf) + 1);
    strncpy(community, buf, strlen(buf) + 1);
    print_help("");
    clear_main(0);
*/
    print_top(NULL, _("Access Points Search"));
    mvwaddstr(main_sub, 0, 0, SEARCH_HEADER);
    wrefresh(main_sub);

    if ((s1 = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
	print_helperr(ERR_SOCKET);
	goto wait_quit;
    }

    i = f_ifctr = 0;
    fapsa = NULL;

    /*
     * Find all IPs of locally available IPv4 interfaces and corresponding
     * broadcast and/or point-to-point addresses.
     */
    for (;;) {
	if (!(ifbuf_ptr = realloc(ifbuf_ptr, ilen))) {
	    print_helperr(_("realloc() error."));
	    goto wait_quit;
	}

	ifc.ifc_buf = ifbuf_ptr;
	ifc.ifc_len = ilen;
	if (ioctl(s1, SIOCGIFCONF, &ifc) >= 0)
	    /* Stupid condition thx to BSD and 2 SIOCGIFCONF implementations */
	    if (ifc.ifc_len + sizeof(struct ifreq) + 64 < ilen)
		break;

	if (ilen > 200000) {
	    print_helperr(_("Network interface discovery error."));
	    goto wait_quit;
	}
	ilen += 100 + (ilen >> 2);
    }
    ifrec_ptr = ifbuf_ptr;
    while (ifrec_ptr < ifbuf_ptr + ifc.ifc_len) {
	memset(&from.sin_addr, 0, sizeof(struct in_addr));
	memset(&to.sin_addr, 0, sizeof(struct in_addr));
	ifr = (struct ifreq *) ifrec_ptr;
#ifdef HAVE_SA_LEN
	ilen = sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;
	if (ilen < sizeof(*ifr))
	    ilen = sizeof(*ifr);

	if (ifr->ifr_addr.sa_family == AF_INET)
	    if (ioctl(s1, SIOCGIFFLAGS, ifrec_ptr) == 0)
		if (ifr->ifr_flags & IFF_UP)
		    if (!(ifr->ifr_flags & IFF_LOOPBACK)) {
			from.sin_addr =
			((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr;
			if (ifr->ifr_flags & IFF_POINTOPOINT) {
			  ioctl(s1, SIOCGIFDSTADDR, ifrec_ptr);
			  to.sin_addr =
			  ((struct sockaddr_in *)&ifr->ifr_dstaddr)->sin_addr;
			} else {
			  ioctl(s1, SIOCGIFBRDADDR, ifrec_ptr);
			  to.sin_addr =
			  ((struct sockaddr_in *)&ifr->ifr_broadaddr)->sin_addr;
			}
		    }
#else
	ilen = sizeof(*ifr);

	if (ioctl(s1, SIOCGIFFLAGS, ifrec_ptr) == 0)
	    if (ifr->ifr_flags & IFF_UP)
		if (!(ifr->ifr_flags & IFF_LOOPBACK)) {
		    if (ifr->ifr_flags & IFF_POINTOPOINT) {
			ioctl(s1, SIOCGIFDSTADDR, ifrec_ptr);
			to.sin_addr =
			((struct sockaddr_in *)&ifr->ifr_dstaddr)->sin_addr;
		    } else {
			ioctl(s1, SIOCGIFBRDADDR, ifrec_ptr);
			to.sin_addr =
			((struct sockaddr_in *)&ifr->ifr_broadaddr)->sin_addr;
		    }
		    if (ioctl(s1, SIOCGIFADDR, ifrec_ptr) == 0)
			if (ifr->ifr_addr.sa_family == AF_INET)
			    from.sin_addr =
			    ((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr;
		}
#endif
	if (from.sin_addr.s_addr && to.sin_addr.s_addr) {
	    f_ifctr++;
	    from.sin_family = to.sin_family = AF_INET;
	    from.sin_port = INADDR_ANY;
	    to.sin_port = htons(161);
	    scan_local_segment(&from, &to, ifr->ifr_name);
	    clear_main_new(LAST_ROW - 5, LAST_ROW);
	}
	ifrec_ptr += ilen;
	/* No need to process next interfaces if the table is already full, */
	if (i == MAX_APS)
	    break;

	/* or if someone pressed 'Q'. */
	if (q_pressed) {
	    q_pressed = 0;
	    break;
	}
    }

    close(s1);

    if (!f_ifctr) {
	print_helperr(_("No local network interfaces found. Press any key."));
    } else if (!i) {
	print_help(_("No directly reachable Access Points found. "
	    "Press any key."));
    } else {
	if (i == MAX_APS)
	    mvwaddstr(main_sub, LAST_ROW - 1, 1,
		_("Single-screen maximum number of APs found."));

	wrefresh(main_sub);
	print_help(_("# - connect to AP; Q - quit"));
	while (1)
	    switch (ac = getch()) {
		case 'Q':
		case 'q':
		    goto quit;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		    if (ac - '0' > (i - 1))
			continue;

		    print_top(NULL, NULL);
		    clear_main(0);
/*		    free(old_community); */
		    connect_options(fapsa[ac - '0'].ip.s_addr,
			fapsa[ac - '0'].type + 1);
		    free(fapsa);
		    return;
	    }
    }

wait_quit:
    getch();
quit:
    if (ifbuf_ptr)
	free(ifbuf_ptr);

    if (fapsa)
	free(fapsa);
/*
    free(community);
    community = old_community;
*/
    print_help("");
    print_top(NULL, NULL);
    clear_main(0);
}
