/*
 *	Wireless Tools
 *
 *		Jean II - HPL '01
 *
 * Just print the ESSID or NWID...
 *
 * This file is released under the GPL license.
 *     Copyright (c) 1997-2004 Jean Tourrilhes <jt@hpl.hp.com>
 */

#include "iwlib-private.h"		/* Private header */

#include <getopt.h>

/*
 * Note on Pcmcia Schemes :
 * ----------------------
 *	The purpose of this tool is to use the ESSID discovery mechanism
 * to select the appropriate Pcmcia Scheme. The card tell us which
 * ESSID it has found, and we can then select the appropriate Pcmcia
 * Scheme for this ESSID (Wireless config (encrypt keys) and IP config).
 *	The way to do it is as follows :
 *			cardctl scheme "essidany"
 *			delay 100
 *			$scheme = iwgetid --scheme
 *			cardctl scheme $scheme
 *	Of course, you need to add a scheme called "essidany" with the
 * following setting :
 *			essidany,*,*,*)
 *				ESSID="any"
 *				IPADDR="10.0.0.1"
 *
 *	This can also be integrated int he Pcmcia scripts.
 *	Some drivers don't activate the card up to "ifconfig up".
 * Therefore, they wont scan ESSID up to this point, so we can't
 * read it reliably in Pcmcia scripts.
 *	I guess the proper way to write the network script is as follows :
 *			if($scheme == "iwgetid") {
 *				iwconfig $name essid any
 *				iwconfig $name nwid any
 *				ifconfig $name up
 *				delay 100
 *				$scheme = iwgetid $name --scheme
 *				ifconfig $name down
 *			}
 *
 *	This is pseudo code, but you get an idea...
 *	The "ifconfig up" activate the card.
 *	The "delay" is necessary to let time for the card scan the
 * frequencies and associate with the AP.
 *	The "ifconfig down" is necessary to allow the driver to optimise
 * the wireless parameters setting (minimise number of card resets).
 *
 *	Another cute idea is to have a list of Pcmcia Schemes to try
 * and to keep the first one that associate (AP address != 0). This
 * would be necessary for closed networks and cards that can't
 * discover essid...
 *
 * Jean II - 29/3/01
 */

/**************************** CONSTANTS ****************************/

#define FORMAT_DEFAULT	0	/* Nice looking display for the user */
#define FORMAT_SCHEME	1	/* To be used as a Pcmcia Scheme */
#define FORMAT_RAW	2	/* Raw value, for shell scripts */
#define WTYPE_ESSID	0	/* Display ESSID or NWID */
#define WTYPE_AP	1	/* Display AP/Cell Address */
#define WTYPE_FREQ	2	/* Display frequency/channel */
#define WTYPE_CHANNEL	3	/* Display channel (converted from freq) */
#define WTYPE_MODE	4	/* Display mode */
#define WTYPE_PROTO	5	/* Display protocol name */

/************************ DISPLAY ESSID/NWID ************************/

/*------------------------------------------------------------------*/
/*
 * Display the ESSID if possible
 */
static int
print_essid(int			skfd,
	    const char *	ifname,
	    int			format)
{
  struct iwreq		wrq;
  char			essid[IW_ESSID_MAX_SIZE + 2];	/* ESSID */
  char			pessid[4 * IW_ESSID_MAX_SIZE + 1];	/* Printable format */
  unsigned int		i;
  unsigned int		j;

  /* Make sure ESSID is always NULL terminated */
  memset(essid, 0, sizeof(essid));

  /* Get ESSID */
  wrq.u.essid.pointer = (caddr_t) essid;
  wrq.u.essid.length = IW_ESSID_MAX_SIZE + 2;
  wrq.u.essid.flags = 0;
  if(iw_get_ext(skfd, ifname, SIOCGIWESSID, &wrq) < 0)
    return(-1);

  switch(format)
    {
    case FORMAT_SCHEME:
      /* Strip all white space and stuff */
      j = 0;
      for(i = 0; i < wrq.u.essid.length; i++)
	if(isalnum(essid[i]))
	  pessid[j++] = essid[i];
      pessid[j] = '\0';
      if((j == 0) || (j > 32))
	return(-2);
      printf("%s\n", pessid);
      break;
    case FORMAT_RAW:
      printf("%s\n", essid);
      break;
    default:
      iw_essid_escape(pessid, essid, wrq.u.essid.length);
      printf("%-8.16s  ESSID:\"%s\"\n", ifname, pessid);
      break;
    }

  return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the NWID if possible
 */
static int
print_nwid(int		skfd,
	   const char *	ifname,
	   int		format)
{
  struct iwreq		wrq;

  /* Get network ID */
  if(iw_get_ext(skfd, ifname, SIOCGIWNWID, &wrq) < 0)
    return(-1);

  switch(format)
    {
    case FORMAT_SCHEME:
      /* Prefix with nwid to avoid name space collisions */
      printf("nwid%X\n", wrq.u.nwid.value);
      break;
    case FORMAT_RAW:
      printf("%X\n", wrq.u.nwid.value);
      break;
    default:
      printf("%-8.16s  NWID:%X\n", ifname, wrq.u.nwid.value);
      break;
    }

  return(0);
}

/**************************** AP ADDRESS ****************************/

/*------------------------------------------------------------------*/
/*
 * Display the AP Address if possible
 */
static int
print_ap(int		skfd,
	 const char *	ifname,
	 int		format)
{
  struct iwreq		wrq;
  char			buffer[64];

  /* Get AP Address */
  if(iw_get_ext(skfd, ifname, SIOCGIWAP, &wrq) < 0)
    return(-1);

  /* Print */
  iw_ether_ntop((const struct ether_addr *) wrq.u.ap_addr.sa_data, buffer);
  switch(format)
    {
    case FORMAT_SCHEME:
      /* I think ':' are not problematic, because Pcmcia scripts
       * seem to handle them properly... */
    case FORMAT_RAW:
      printf("%s\n", buffer);
      break;
    default:
      printf("%-8.16s  Access Point/Cell: %s\n", ifname, buffer);
      break;
    }

  return(0);
}

/****************************** OTHER ******************************/

/*------------------------------------------------------------------*/
/*
 * Display the frequency (or channel) if possible
 */
static int
print_freq(int		skfd,
	   const char *	ifname,
	   int		format)
{
  struct iwreq		wrq;
  double		freq;
  char			buffer[64];

  /* Get frequency / channel */
  if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) < 0)
    return(-1);

  /* Print */
  freq = iw_freq2float(&(wrq.u.freq));
  switch(format)
    {
    case FORMAT_SCHEME:
      /* Prefix with freq to avoid name space collisions */
      printf("freq%g\n", freq);
      break;
    case FORMAT_RAW:
      printf("%g\n", freq);
      break;
    default:
      iw_print_freq(buffer, sizeof(buffer), freq, -1, wrq.u.freq.flags);
      printf("%-8.16s  %s\n", ifname, buffer);
      break;
    }

  return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the channel (converted from frequency) if possible
 */
static int
print_channel(int		skfd,
	      const char *	ifname,
	      int		format)
{
  struct iwreq		wrq;
  struct iw_range	range;
  double		freq;
  int			channel;

  /* Get frequency / channel */
  if(iw_get_ext(skfd, ifname, SIOCGIWFREQ, &wrq) < 0)
    return(-1);

  /* Convert to channel */
  if(iw_get_range_info(skfd, ifname, &range) < 0)
    return(-2);
  freq = iw_freq2float(&(wrq.u.freq));
  if(freq < KILO)
    channel = (int) freq;
  else
    {
      channel = iw_freq_to_channel(freq, &range);
      if(channel < 0)
	return(-3);
    }

  /* Print */
  switch(format)
    {
    case FORMAT_SCHEME:
      /* Prefix with freq to avoid name space collisions */
      printf("channel%d\n", channel);
      break;
    case FORMAT_RAW:
      printf("%d\n", channel);
      break;
    default:
      printf("%-8.16s  Channel:%d\n", ifname, channel);
      break;
    }

  return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the mode if possible
 */
static int
print_mode(int		skfd,
	   const char *	ifname,
	   int		format)
{
  struct iwreq		wrq;

  /* Get frequency / channel */
  if(iw_get_ext(skfd, ifname, SIOCGIWMODE, &wrq) < 0)
    return(-1);
  if(wrq.u.mode >= IW_NUM_OPER_MODE)
    return(-2);

  /* Print */
  switch(format)
    {
    case FORMAT_SCHEME:
      /* Strip all white space and stuff */
      if(wrq.u.mode == IW_MODE_ADHOC)
	printf("AdHoc\n");
      else
	printf("%s\n", iw_operation_mode[wrq.u.mode]);
      break;
    case FORMAT_RAW:
      printf("%d\n", wrq.u.mode);
      break;
    default:
      printf("%-8.16s  Mode:%s\n", ifname, iw_operation_mode[wrq.u.mode]);
      break;
    }

  return(0);
}

/*------------------------------------------------------------------*/
/*
 * Display the ESSID if possible
 */
static int
print_protocol(int		skfd,
	       const char *	ifname,
	       int		format)
{
  struct iwreq		wrq;
  char			proto[IFNAMSIZ + 1];	/* Protocol */
  char			pproto[IFNAMSIZ + 1];	/* Pcmcia format */
  unsigned int		i;
  unsigned int		j;

  /* Get Protocol name */
  if(iw_get_ext(skfd, ifname, SIOCGIWNAME, &wrq) < 0)
    return(-1);
  strncpy(proto, wrq.u.name, IFNAMSIZ);
  proto[IFNAMSIZ] = '\0';

  switch(format)
    {
    case FORMAT_SCHEME:
      /* Strip all white space and stuff */
      j = 0;
      for(i = 0; i < strlen(proto); i++)
	if(isalnum(proto[i]))
	  pproto[j++] = proto[i];
      pproto[j] = '\0';
      if((j == 0) || (j > 32))
	return(-2);
      printf("%s\n", pproto);
      break;
    case FORMAT_RAW:
      printf("%s\n", proto);
      break;
    default:
      printf("%-8.16s  Protocol Name:\"%s\"\n", ifname, proto);
      break;
    }

  return(0);
}

/******************************* MAIN ********************************/

/*------------------------------------------------------------------*/
/*
 * Check options and call the proper handler
 */
static int
print_one_device(int		skfd,
		 int		format,
		 int		wtype,
		 const char*	ifname)
{
  int ret;

  /* Check wtype */
  switch(wtype)
    {
    case WTYPE_AP:
      /* Try to print an AP */
      ret = print_ap(skfd, ifname, format);
      break;

    case WTYPE_CHANNEL:
      /* Try to print channel */
      ret = print_channel(skfd, ifname, format);
      break;

    case WTYPE_FREQ:
      /* Try to print frequency */
      ret = print_freq(skfd, ifname, format);
      break;

    case WTYPE_MODE:
      /* Try to print the mode */
      ret = print_mode(skfd, ifname, format);
      break;

    case WTYPE_PROTO:
      /* Try to print the protocol */
      ret = print_protocol(skfd, ifname, format);
      break;

    default:
      /* Try to print an ESSID */
      ret = print_essid(skfd, ifname, format);
      if(ret < 0)
	{
	  /* Try to print a nwid */
	  ret = print_nwid(skfd, ifname, format);
	}
    }

  return(ret);
}

/*------------------------------------------------------------------*/
/*
 * Try the various devices until one return something we can use
 *
 * Note : we can't use iw_enum_devices() because we want a different
 * behaviour :
 *	1) Stop at the first valid wireless device
 *	2) Only go through active devices
 */
static int
scan_devices(int		skfd,
	     int		format,
	     int		wtype)
{
  char		buff[1024];
  struct ifconf ifc;
  struct ifreq *ifr;
  int		i;

  /* Get list of active devices */
  ifc.ifc_len = sizeof(buff);
  ifc.ifc_buf = buff;
  if(ioctl(skfd, SIOCGIFCONF, &ifc) < 0)
    {
      perror("SIOCGIFCONF");
      return(-1);
    }
  ifr = ifc.ifc_req;

  /* Print the first match */
  for(i = ifc.ifc_len / sizeof(struct ifreq); --i >= 0; ifr++)
    {
      if(print_one_device(skfd, format, wtype, ifr->ifr_name) >= 0)
	return 0;
    }
  return(-1);
}

/*------------------------------------------------------------------*/
/*
 * helper
 */
static void
iw_usage(int status)
{
  fputs("Usage iwgetid [OPTIONS] [ifname]\n"
	"  Options are:\n"
	"    -a,--ap       Print the access point address\n"
	"    -c,--channel  Print the current channel\n"
	"    -f,--freq     Print the current frequency\n"
	"    -m,--mode     Print the current mode\n"
	"    -p,--protocol Print the protocol name\n"
	"    -r,--raw      Format the output as raw value for shell scripts\n"
	"    -s,--scheme   Format the output as a PCMCIA scheme identifier\n"
	"    -h,--help     Print this message\n",
	status ? stderr : stdout);
  exit(status);
}

static const struct option long_opts[] = {
  { "ap", no_argument, NULL, 'a' },
  { "channel", no_argument, NULL, 'c' },
  { "freq", no_argument, NULL, 'f' },
  { "mode", no_argument, NULL, 'm' },
  { "protocol", no_argument, NULL, 'p' },
  { "help", no_argument, NULL, 'h' },
  { "raw", no_argument, NULL, 'r' },
  { "scheme", no_argument, NULL, 's' },
  { NULL, 0, NULL, 0 }
};

/*------------------------------------------------------------------*/
/*
 * The main !
 */
int
main(int	argc,
     char **	argv)
{
  int	skfd;			/* generic raw socket desc.	*/
  int	format = FORMAT_DEFAULT;
  int	wtype = WTYPE_ESSID;
  int	opt;
  int	ret = -1;

  /* Check command line arguments */
  while((opt = getopt_long(argc, argv, "acfhmprs", long_opts, NULL)) > 0)
    {
      switch(opt)
	{
	case 'a':
	  /* User wants AP/Cell Address */
	  wtype = WTYPE_AP;
	  break;

	case 'c':
	  /* User wants channel only */
	  wtype = WTYPE_CHANNEL;
	  break;

	case 'f':
	  /* User wants frequency/channel */
	  wtype = WTYPE_FREQ;
	  break;

	case 'm':
	  /* User wants the mode */
	  wtype = WTYPE_MODE;
	  break;

	case 'p':
	  /* User wants the protocol */
	  wtype = WTYPE_PROTO;
	  break;

	case 'h':
	  iw_usage(0);
	  break;

	case 'r':
	  /* User wants a Raw format */
	  format = FORMAT_RAW;
	  break;

	case 's':
	  /* User wants a Scheme format */
	  format = FORMAT_SCHEME;
	  break;

	default:
	  iw_usage(1);
	  break;
	}
    }
  if(optind + 1 < argc) {
    fputs("Too many arguments.\n", stderr);
    iw_usage(1);
  }

  /* Create a channel to the NET kernel. */
  if((skfd = iw_sockets_open()) < 0)
    {
      perror("socket");
      return(-1);
    }

  /* Check if first argument is a device name */
  if(optind < argc)
    {
      /* Yes : query only this device */
      ret = print_one_device(skfd, format, wtype, argv[optind]);
    }
  else
    {
      /* No : query all devices and print first found */
      ret = scan_devices(skfd, format, wtype);
    }

  fflush(stdout);
  iw_sockets_close(skfd);
  return(ret);
}
